/*
 * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
 * 
 * http://izpack.org/
 * http://izpack.codehaus.org/
 * 
 * Copyright 2002 Elmar Grom
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.izforge.izpack.gui;

import java.awt.*;

/*---------------------------------------------------------------------------*/

/**
 * A flow layout arranges components in a left-to-right flow, much like lines of text in a
 * paragraph. Flow layouts are typically used to arrange buttons in a panel. It will arrange buttons
 * left to right until no more buttons fit on the same line. Each line is centered.
 * <p/>
 * For example, the following picture shows an applet using the flow layout manager (its default
 * layout manager) to position three buttons:
 * <p/>
 * <img src="doc-files/FlowLayout-1.gif" ALT="Graphic of Layout for Three Buttons" ALIGN=center
 * HSPACE=10 VSPACE=7>
 * <p/>
 * Here is the code for this applet:
 * <p/>
 * <hr>
 * <blockquote>
 * <p/>
 * <pre>
 * import java.awt.*;
 * import java.applet.Applet;
 * <p/>
 * public class myButtons extends Applet
 * {
 * <p/>
 *     Button button1, button2, button3;
 * <p/>
 *     public void init()
 *     {
 *         button1 = new Button(&quot;Ok&quot;);
 *         button2 = new Button(&quot;Open&quot;);
 *         button3 = new Button(&quot;Close&quot;);
 *         add(button1);
 *         add(button2);
 *         add(button3);
 *     }
 * }
 * <p/>
 * </pre>
 * <p/>
 * </blockquote>
 * <hr>
 * <p/>
 * A flow layout lets each component assume its natural (preferred) size.
 * <p/>
 * This class is a bit different from java.awt.FlowLayout. <blockquote> java.awt.FlowLayout has a
 * minor problem that was bugging me when I wrote the UserInputPanel. FlowLayout puts some amount of
 * space in between each component that it lays out. In addition it adds that same amount of space
 * to the left and to the right of the entire group. Therefore items such as the RuleInputfield that
 * are laid out with a FlowLayout would never line up properly with the other components (it would
 * appear to be slightly indented). Because there is no way to circumvent this behavior in
 * FlowLayout (it's hard coded) I copied the source and modified it so that it does not add the
 * space to the left and to the right. Now my stuff lines up properly. (Elmar Grom)</blockquote>
 *
 * @author Arthur van Hoff
 * @author Sami Shaio
 * @author Elmar Grom
 * @version 1.39, 11/29/02
 */
/*---------------------------------------------------------------------------*/
public class FlowLayout implements LayoutManager
{

    /**
     * This value indicates that each row of components should be left-justified.
     */
    public static final int LEFT = 0;

    /**
     * This value indicates that each row of components should be centered.
     */
    public static final int CENTER = 1;

    /**
     * This value indicates that each row of components should be right-justified.
     */
    public static final int RIGHT = 2;

    /**
     * This value indicates that each row of components should be justified to the leading edge of
     * the container's orientation, for example, to the left in left-to-right orientations.
     *
     * @see java.awt.Component#getComponentOrientation
     * @see java.awt.ComponentOrientation
     * @since 1.2 Package-private pending API change approval
     */
    public static final int LEADING = 3;

    /**
     * This value indicates that each row of components should be justified to the leading edge of
     * the container's orientation, for example, to the right in left-to-right orientations.
     *
     * @see java.awt.Component#getComponentOrientation
     * @see java.awt.ComponentOrientation
     * @since 1.2 Package-private pending API change approval
     */
    public static final int TRAILING = 4;

    /**
     * <code>align</code> is the proprty that determines how each row distributes empty space. It
     * can be one of the following three values : <code>LEFT</code>
     * <code>RIGHT</code>
     * <code>CENTER</code>
     *
     * @serial
     * @see #getAlignment
     * @see #setAlignment
     */
    int align; // This is for 1.1 serialization compatibilitys

    /**
     * <code>newAlign</code> is the property that determines how each row distributes empty space
     * for the Java 2 platform, v1.2 and greater. It can be one of the following three values :
     * <code>LEFT</code>
     * <code>RIGHT</code>
     * <code>CENTER</code>
     *
     * @serial
     * @see #getAlignment
     * @see #setAlignment
     * @since 1.2
     */
    int newAlign; // This is the one we actually use

    /**
     * The flow layout manager allows a seperation of components with gaps. The horizontal gap will
     * specify the space between components.
     *
     * @serial
     * @see #getHgap
     * @see #setHgap
     */
    int hgap;

    /**
     * The flow layout manager allows a seperation of components with gaps. The vertical gap will
     * specify the space between rows.
     *
     * @serial
     * @see #getVgap
     * @see #setVgap
     */
    int vgap;

    /*--------------------------------------------------------------------------*/

    /**
     * Constructs a new Flow Layout with a centered alignment and a default 5-unit horizontal and
     * vertical gap.
     */
    /*--------------------------------------------------------------------------*/
    public FlowLayout()
    {
        this(CENTER, 5, 5);
    }

    /*--------------------------------------------------------------------------*/

    /**
     * Constructs a new Flow Layout with the specified alignment and a default 5-unit horizontal and
     * vertical gap. The value of the alignment argument must be one of <code>FlowLayout.LEFT</code>,
     * <code>FlowLayout.RIGHT</code>, or <code>FlowLayout.CENTER</code>.
     *
     * @param align the alignment value
     */
    /*--------------------------------------------------------------------------*/
    public FlowLayout(int align)
    {
        this(align, 5, 5);
    }

    /*--------------------------------------------------------------------------*/

    /**
     * Creates a new flow layout manager with the indicated alignment and the indicated horizontal
     * and vertical gaps.
     * <p/>
     * The value of the alignment argument must be one of <code>FlowLayout.LEFT</code>,
     * <code>FlowLayout.RIGHT</code>, or <code>FlowLayout.CENTER</code>.
     *
     * @param align the alignment value.
     * @param hgap  the horizontal gap between components.
     * @param vgap  the vertical gap between components.
     */
    /*--------------------------------------------------------------------------*/
    public FlowLayout(int align, int hgap, int vgap)
    {
        this.hgap = hgap;
        this.vgap = vgap;
        setAlignment(align);
    }

    /*--------------------------------------------------------------------------*/

    /**
     * Gets the alignment for this layout. Possible values are <code>FlowLayout.LEFT</code>,
     * <code>FlowLayout.RIGHT</code>, or <code>FlowLayout.CENTER</code>.
     *
     * @return the alignment value for this layout.
     * @see java.awt.FlowLayout#setAlignment
     */
    /*--------------------------------------------------------------------------*/
    public int getAlignment()
    {
        return (newAlign);
    }

    /*--------------------------------------------------------------------------*/

    /**
     * Sets the alignment for this layout. Possible values are <code>FlowLayout.LEFT</code>,
     * <code>FlowLayout.RIGHT</code>, and <code>FlowLayout.CENTER</code>.
     *
     * @param align the alignment value.
     * @see #getAlignment()
     */
    /*--------------------------------------------------------------------------*/
    public void setAlignment(int align)
    {
        this.newAlign = align;

        // this.align is used only for serialization compatibility,
        // so set it to a value compatible with the 1.1 version
        // of the class

        switch (align)
        {
            case LEADING:
                this.align = LEFT;
                break;
            case TRAILING:
                this.align = RIGHT;
                break;
            default:
                this.align = align;
                break;
        }
    }

    /*--------------------------------------------------------------------------*/

    /**
     * Gets the horizontal gap between components.
     *
     * @return the horizontal gap between components.
     * @see #setHgap(int)
     */
    /*--------------------------------------------------------------------------*/
    public int getHgap()
    {
        return (hgap);
    }

    /*--------------------------------------------------------------------------*/

    /**
     * Sets the horizontal gap between components.
     *
     * @param hgap the horizontal gap between components
     * @see #getHgap()
     */
    /*--------------------------------------------------------------------------*/
    public void setHgap(int hgap)
    {
        this.hgap = hgap;
    }

    /*--------------------------------------------------------------------------*/

    /**
     * Gets the vertical gap between components.
     *
     * @return the vertical gap between components.\
     * @see #setVgap(int)
     */
    /*--------------------------------------------------------------------------*/
    public int getVgap()
    {
        return (vgap);
    }

    /*--------------------------------------------------------------------------*/

    /**
     * Sets the vertical gap between components.
     *
     * @param vgap the vertical gap between components
     * @see #getVgap()
     */
    /*--------------------------------------------------------------------------*/
    public void setVgap(int vgap)
    {
        this.vgap = vgap;
    }

    /*--------------------------------------------------------------------------*/

    /**
     * Adds the specified component to the layout. Not used by this class.
     *
     * @param name the name of the component
     * @param comp the component to be added
     */
    /*--------------------------------------------------------------------------*/
    public void addLayoutComponent(String name, Component comp)
    {
    }

    /*--------------------------------------------------------------------------*/

    /**
     * Removes the specified component from the layout. Not used by this class.
     *
     * @param comp the component to remove
     */
    /*--------------------------------------------------------------------------*/
    public void removeLayoutComponent(Component comp)
    {
    }

    /*--------------------------------------------------------------------------*/

    /**
     * Returns the preferred dimensions for this layout given the components in the specified target
     * container.
     *
     * @param target the component which needs to be laid out
     * @return the preferred dimensions to lay out the subcomponents of the specified container.
     * @see #minimumLayoutSize(Container)
     */
    /*--------------------------------------------------------------------------*/
    public Dimension preferredLayoutSize(Container target)
    {
        synchronized (target.getTreeLock())
        {
            Dimension dimension = new Dimension(0, 0);
            int nmembers = target.getComponentCount();
            boolean firstVisibleComponent = true;

            for (int i = 0; i < nmembers; i++)
            {
                Component component = target.getComponent(i);
                if (component.isVisible())
                {
                    Dimension preferredSize = component.getPreferredSize();
                    dimension.height = Math.max(dimension.height, preferredSize.height);
                    if (firstVisibleComponent)
                    {
                        firstVisibleComponent = false;
                    }
                    else
                    {
                        dimension.width += hgap;
                    }
                    dimension.width += preferredSize.width;
                }
            }

            Insets insets = target.getInsets();
            dimension.width += insets.left + insets.right + hgap * 2;
            dimension.height += insets.top + insets.bottom + vgap * 2;

            return (dimension);
        }
    }

    /*--------------------------------------------------------------------------*/

    /**
     * Returns the minimum dimensions needed to layout the components contained in the specified
     * target container.
     *
     * @param target the component which needs to be laid out
     * @return the minimum dimensions to lay out the subcomponents of the specified container.
     * @see #preferredLayoutSize(Container)
     */
    /*--------------------------------------------------------------------------*/
    public Dimension minimumLayoutSize(Container target)
    {
        synchronized (target.getTreeLock())
        {
            Dimension dimension = new Dimension(0, 0);
            int nmembers = target.getComponentCount();

            for (int i = 0; i < nmembers; i++)
            {
                Component component = target.getComponent(i);
                if (component.isVisible())
                {
                    Dimension minimumSize = component.getMinimumSize();
                    dimension.height = Math.max(dimension.height, minimumSize.height);
                    if (i > 0)
                    {
                        dimension.width += hgap;
                    }
                    dimension.width += minimumSize.width;
                }
            }

            Insets insets = target.getInsets();
            dimension.width += insets.left + insets.right + hgap * 2;
            dimension.height += insets.top + insets.bottom + vgap * 2;

            return (dimension);
        }
    }

    /*--------------------------------------------------------------------------*/

    /**
     * Centers the elements in the specified row, if there is any slack.
     *
     * @param target   the component which needs to be moved
     * @param x        the x coordinate
     * @param y        the y coordinate
     * @param width    the width dimensions
     * @param height   the height dimensions
     * @param rowStart the beginning of the row
     * @param rowEnd   the the ending of the row
     */
    /*--------------------------------------------------------------------------*/
    private void moveComponents(Container target, int x, int y, int width, int height,
                                int rowStart, int rowEnd, boolean ltr)
    {
        synchronized (target.getTreeLock())
        {
            switch (newAlign)
            {
                case LEFT:
                    x += ltr ? 0 : width;
                    break;
                case CENTER:
                    x += width / 2;
                    break;
                case RIGHT:
                    x += ltr ? width : 0;
                    break;
                case LEADING:
                    break;
                case TRAILING:
                    x += width;
                    break;
            }

            for (int i = rowStart; i < rowEnd; i++)
            {
                Component component = target.getComponent(i);

                if (component.isVisible())
                {
                    if (ltr)
                    {
                        component.setLocation(x, y + (height - component.getSize().height) / 2);
                    }
                    else
                    {
                        component.setLocation(target.getSize().width - x - component.getSize().width, y
                                + (height - component.getSize().height) / 2);
                    }

                    x += component.getSize().width + hgap;
                }
            }
        }
    }

    /*--------------------------------------------------------------------------*/

    /**
     * Lays out the container. This method lets each component take its preferred size by reshaping
     * the components in the target container in order to satisfy the constraints of this
     * <code>FlowLayout</code> object.
     *
     * @param target the specified component being laid out.
     */
    /*--------------------------------------------------------------------------*/
    public void layoutContainer(Container target)
    {
        synchronized (target.getTreeLock())
        {
            Insets insets = target.getInsets();
            int maxWidth = target.getSize().width - (insets.left + insets.right + hgap * 2);
            int nMembers = target.getComponentCount();
            int x = 0;
            int y = insets.top + vgap;
            int rowh = 0;
            int start = 0;

            boolean ltr = target.getComponentOrientation().isLeftToRight();

            for (int i = 0; i < nMembers; i++)
            {
                Component component = target.getComponent(i);

                if (component.isVisible())
                {
                    Dimension preferredSize = component.getPreferredSize();
                    component.setSize(preferredSize.width, preferredSize.height);

                    if ((x == 0) || ((x + preferredSize.width) <= maxWidth))
                    {
                        if (x > 0)
                        {
                            x += hgap;
                        }
                        x += preferredSize.width;
                        rowh = Math.max(rowh, preferredSize.height);
                    }
                    else
                    {
                        moveComponents(target, insets.left, y, maxWidth - x, rowh, start, i, ltr);
                        x = preferredSize.width;
                        y += vgap + rowh;
                        rowh = preferredSize.height;
                        start = i;
                    }
                }
            }

            moveComponents(target, insets.left, y, maxWidth - x, rowh, start, nMembers, ltr);
        }
    }

    /*--------------------------------------------------------------------------*/

    /**
     * Returns a string representation of this <code>FlowLayout</code> object and its values.
     *
     * @return a string representation of this layout.
     */
    /*--------------------------------------------------------------------------*/
    public String toString()
    {
        String str = "";

        switch (align)
        {
            case LEFT:
                str = ",align=left";
                break;
            case CENTER:
                str = ",align=center";
                break;
            case RIGHT:
                str = ",align=right";
                break;
            case LEADING:
                str = ",align=leading";
                break;
            case TRAILING:
                str = ",align=trailing";
                break;
        }

        return (getClass().getName() + "[hgap=" + hgap + ",vgap=" + vgap + str + "]");
    }
}
/*---------------------------------------------------------------------------*/
